iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
Modern Web

設計系統 - Design System系列 第 7

[Day 7] Design System - FocusScope 組件

  • 分享至 

  • xImage
  •  

本系列文章會在筆者的部落格繼續連載!Design System 101 感謝大家的閱讀!

原本今日計畫寫的內容是關於 Design System 文檔建置,但鑑於上一篇組件也是 Accessibility 相關的 Visually Hidden,決定一氣呵成介紹另一個 Accessibility 相關的組件 - FocusScope。

繼上次介紹完 Accessibility 後,了解到建立無障礙網頁的重要性,且明白使用者不一定是透過滑鼠來操作網頁,或許是透過鍵盤來操作,所以今天要來介紹一個在鍵盤操作上的重要概念 - Focus Management.

什麼是 Focus Scope

在討論 Focus Scope 之前,我們首先要了解什麼是「focus」。簡單來說,「focus」是 UI 中的一個重要概念,它指的是當前正在被使用者操作或對話的元素。

Accessible focus management is the practice of using programmatic focus changes to enhance comprehension and usability of a website. -- Cloudscape.design

Focus Scope 顧名思義,就是將 focus 限制在某個範圍內。最常看見的例子就是用在 Modal 組件上。當 Modal 打開時,我們希望使用者只能 focus 在 Modal 內的元素,而不能 focus 到 Modal 以外的元素。

為什麼需要 Focus Scope

想像一下,當今天我們是透過鍵盤來操作介面的使用者,我們可能需要透過 tab 鍵 focus 到下一個 focusable 的元素。

畢竟網頁不可能只有一層 Layer。可能你點擊某個 Button 會跳出 Modal, Dropdown Menu 等等對話窗形式的組件。對於滑鼠的使用者,他們可以很輕鬆自然地互動這類型的組件。

但對於鍵盤使用者,如果沒有自動將可 focus 的範圍限縮到對話窗式的組件內,則使用者將無法互動到這些組件內的元素。

因此,開發者通常會在這些組件上加入 <FocusScope>。當對話窗形式的組件打開時,自動的 focus 到組件內的元素,而關掉組件時則 restore 到原本的 Button 元素。

設計 FocusScope

接下來,我將介紹如何設計一個 FocusScope 組件,並且提供一個 hook 讓其他開發者可以透過它來控制 focus 的行為。

需求

整理一下我們目前的需求:

  1. 當 Modal 開啟時,將 button 組件的 reference 保存在 state 內
  2. 自動 focus 到 Modal 內的元素
  3. 當使用者透過鍵盤的 tab 去 focus 元素時,不會 focus 到 Modal 以外的元素
  4. Modal 關掉後我們要會 restore 到原本的 button 元素

舉 Radix UI 的 Dialog 為例,一個好的 Dialog 應該要可以透過鍵盤操作完成所有行為
*舉 Radix UI 的 Dialog 為例,一個好的 Dialog 應該要可以透過鍵盤操作完成所有行為

綜觀架構

如果將上述的問題拆解,可以將問題拆解成兩個部分:

  1. 取得特定範圍內的 focusable 元素 (例如 Modal 內的元素)
  2. 能夠控制 focus 的行為 (例如透過 tab 等鍵盤事件控制是否 focus 到下一個元素)

若要提供一個 FocusScope 的組件來解決上述問題,應該要如何設計並且如何應用此組件呢?

React Context Provider

為了取得特定範圍內的 focusable 元素,我們可以用 <span hidden /> 去包住範圍內的元素。並透過迭代所有的子元素,將 focusable 的元素保存在 state 內。接著,我們可以透過 React Context API 將 focus 的操作傳遞下去。

<FocusScope>
  <Component />
</FocusScope>

useFocusManager

並且提供一個 hook 讓其他開發者可以透過它來控制 focus 的行為。

const Component = () => {
  const focusManager = getFocusManager();

  const handleKeyDown = (e) => {
    if (e.key === 'ArrowLeft') focusManager.focusNext();
  };
  // ...
};

API 設計

參數 型別 說明
children ReactNode 容器的內容
restoreFocus boolean 是否恢復到原始焦點
autoFocus boolean 是否自動 focus 內容元素
contain boolean 是否將 focus 限制在容器內

小結

明天會介紹如何實作,大家不仿先嘗試看看實作一個 FocusScope 組件吧!

Github Repo - Focus Scope

透過 plop 來快速產生 FocusScope 組件

> design-system/ pnpm generate // name: focus-scope
> design-system/ cd packages/focus-scope
> design-system/packages/focus-scope/ pnpm i // 安裝相依套件

開啟 Storybook & 測試

> design-system/ pnpm run test -w
> design-system/ pnpm run storybook

透過 changeset 來產生 changelog 以及 commit

> design-system/ pnpm changeset

Reference


上一篇
[Day 6] Design System - 專案建置 (二)與 Visually Hidden 組件
下一篇
[Day 8] Design System - FocusScope 組件 (二)
系列文
設計系統 - Design System30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言